function genUid() {
  const t = URL.createObjectURL(new Blob([]))
  const uid = t.toString().split(/\//).pop()
  URL.revokeObjectURL(t)
  return uid
}

function getPath(path) {
  return path.replace(/^\/?src\/views\/|^\/?@\/views\/|\.vue$/g, '')
}

function getComponentByPath(path) {
  return new Promise((resolve, reject) => {
    require([`@/views/${getPath(path)}.vue`], m => {
      resolve(m.default)
    })
  })
}

const name = 'PageStack'

function findPageStackUp(parent) {
  if (parent.$options.name === name) {
    return parent
  } else if (parent.$parent) {
    return findPageStackUp(parent.$parent)
  }
}

export const PageStackMixin = {
  created() {
    this.$parent && (this.$pageStack = findPageStackUp(this.$parent))
  }
}

export const PageStack = {
  name,
  props: {
    page: String,
    isCloseTag: Boolean
  },
  data() {
    return {
      pageStack: []
    }
  },
  created() {
    const page = this.page
    require([`@/views/${getPath(page)}.vue`], m => {
      const vnode = this.createElement(m.default)
      vnode.data.keepAlive = true
      this.pageStack.push({ vnode })
    })
  },
  watch: {
    pageStack: {
      handler(v) {
        if (this.isCloseTag && v.length <= 0) {
          const l = window.top.location
          window.top && window.top.postMessage({
            flag: 'closetag',
            extraData: {
              fullPath: l.search.replace('?src=', '')
            }
          }, '*')
        }
      }
    }
  },
  computed: {
  },
  render(h) {
    if (this.pageStack.length - 1 < 0) {
      return h('span')
    } else {
      return this.pageStack[this.pageStack.length - 1].vnode
    }
  },
  methods: {
    handler$attrs() {
      if (!this.$attrs) {
        return {}
      }
      const t = {}
      for (const [k, v] of Object.entries(this.$attrs)) {
        t[k.replace(/-\w/g, function(o) {
          return o.replace('-', '').toUpperCase()
        })] = v
      }
      return t
    },
    createElement(component, config) {
      const c = config || {}
      c.props = Object.assign({}, this.handler$attrs(), c.props)
      return this.$createElement(component, c)
    },
    async createVnode(path, config) {
      let component = path
      if (Object.prototype.toString.call(path) === '[object Object]' && typeof path.extends === 'string') {
        component.extends = await getComponentByPath(path.extends)
      } else if (Object.prototype.toString.call(path) === '[object String]') {
        component = await getComponentByPath(path)
      }
      const vnode = this.createElement(component, config)
      vnode.key = genUid()
      vnode.data.keepAlive = true
      return vnode
    },
    async replace(path, config) {
      const vnode = await this.createVnode(path, config)
      this.destroy(this.pageStack.splice(this.pageStack.length - 1, 1, vnode))
    },
    async push(path, config, callback) {
      const vnode = await this.createVnode(path, config)
      this.pageStack.push({ vnode, callback })
    },
    pop(...args) {
      this.destroy(this.pageStack.pop(), ...args)
    },
    go(i) {
      this.destroy(this.pageStack.splice(this.pageStack.length - i - 1, i))
    },
    destroy(vnodes, ...args) {
      if (vnodes instanceof Array) {
        for (const c of vnodes) {
          const { vnode, callback } = c
          vnode.data.keepAlive = false
          vnode.componentInstance && vnode.componentInstance.$destroy()
          callback && callback()
        }
      } else {
        const { vnode, callback } = vnodes
        vnode.data.keepAlive = false
        vnode.componentInstance && vnode.componentInstance.$destroy()
        callback && callback(...args)
      }
    }
  }
}

export default {
  install(Vue, opts) {
    Vue.mixin(PageStackMixin)
    Vue.component('page-stack', PageStack)
  }
}
